////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek, 2010.
// -------------------------------------------------------------------------
//  File name:   CCryTCPService.cpp
//  Version:     v1.00
//  Created:     29/03/2010 by Paul Mikell.
//  Description: CCryTCPService member definitions
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////


#include "StdAfx.h"
#include "Network.h"
#include "CryTCPService.h"
#include "CryTCPServiceFactory.h"





#define SECONDS_BETWEEN_DNS_RETRIES		10
#define RECV_BUFFER_SIZE							4*1024
#define INVALID_SOCKET_INDEX					-1
#define MAX_URL_LENGTH								255
#define MAX_URL_BUF_SIZE							(MAX_URL_LENGTH + 1)

CCryTCPService::CCryTCPService(CCryTCPServiceFactory* pOwner)
:	m_pResolvedAddr( 0 ),
	m_cnt( 0 ),
	m_pOwner( pOwner ),
	m_numSocketsOpen( 0 ),
	m_numSocketsToFree( 0 ),
	m_connectionStatus(eCTCPSCS_NotConnected),
	m_pUrl( 0 ),
	m_dataQueueLength(0),
	m_dataQueueSize(0)
{
}


// Description:
//	 Destructor for CCryTCPService.
/* dtor */
CCryTCPService::~CCryTCPService()
{
	Terminate( true );
	SAFE_DELETE( m_pUrl );
	SAFE_DELETE( m_pResolvedAddr );
}


// Description:
//	 Initialize.
// Arguments:
//	 pUrl - URL
//	 port - port
// Return:
//	 Result code.
ECryTCPServiceResult
CCryTCPService::Initialise(
	const char*				pUrl,
	uint16					port )
{
	ECryTCPServiceResult	result = eCTCPSR_Failed;

	size_t					urlLen;

	if ( pUrl )
	{
		if ( ( urlLen = strlen( pUrl ) ) <= MAX_URL_LENGTH )
		{
			m_pUrl = new char[ urlLen + 1 ];
			strncpy( m_pUrl, pUrl, urlLen + 1 );
			m_pUrl[ urlLen ] = '\0';
			m_port = port;
			m_lastNameReq = time( 0 );
			m_pNameReq = RESOLVER.RequestNameLookup( m_pUrl );

			result = eCTCPSR_Ok;
		}
	}

	return result;
}


// Description:
//	 Terminate.
// Arguments:
//	 isDestructing - is this object's destructor running?
// Return:
//	 Result code.
ECryTCPServiceResult
CCryTCPService::Terminate(
	bool					isDestructing )
{
	m_queueMutex.Lock();

	while ( !m_dataQueue.empty() )
	{
		DiscardQueueElement();
	}

	m_queueMutex.Unlock();

	return eCTCPSR_Ok;
}

void CCryTCPService::DiscardQueueElement()
{
	STCPServiceDataPtr pCurr = m_dataQueue.front();

	if (pCurr->tcpServCb)
	{
		pCurr->tcpServCb( eCTCPSR_Failed, pCurr->pUserArg );
	}
	else
	{
		if (pCurr->tcpServReplyCb)
		{
			pCurr->tcpServReplyCb( eCTCPSR_Failed, pCurr->pUserArg, pCurr, 0, 0, true );
		}
	}

	CloseConnection( pCurr->m_socketIdx );
	m_dataQueueLength--;
	m_dataQueueSize -= pCurr->length;

	m_dataQueue.pop();
}


// Description:
//	 Get address.
// Arguments:
//	 addr - address
// Return:
//	 Result code.
ENameRequestResult
CCryTCPService::GetAddress(
	sockaddr_in&			addr )
{
	ScopedSwitchToGlobalHeap useGlobalHeap;

	ENameRequestResult		result = eNRR_Failed;

	TNetAddressVec			nav;
	SIPv4Addr*				pIpv4Addr = NULL;
	size_t					naddrs = nav.size();

	if ( !m_pResolvedAddr )
	{
		if ( m_pNameReq )
		{
			// Has the DNS lookup completed?

			result = m_pNameReq->GetResult();

			switch ( result )
			{
			case eNRR_Pending:
				m_connectionStatus = eCTCPSCS_Pending;
				break;

			case eNRR_Succeeded:

				m_pNameReq->GetResult( nav );

				pIpv4Addr = NULL;
				naddrs = nav.size();

				for ( size_t i = 0; i < naddrs; ++i )
				{
					TNetAddress netAddr = nav[ i ];

					pIpv4Addr = netAddr.GetPtr<SIPv4Addr>();

					if ( pIpv4Addr )
					{
						pIpv4Addr->port = m_port;
						break;
					}
				}

				if ( pIpv4Addr )
				{
					m_pResolvedAddr = new TNetAddress( *pIpv4Addr );
				}

				m_pNameReq = 0;

				break;

			case eNRR_Failed:
				m_connectionStatus = eCTCPSCS_NotConnectedDNSFailed;
				m_pNameReq = 0;

				break;
			}
		}
		else
		{
			// Retry.

			time_t					now = time( 0 );

			if ( now > m_lastNameReq + SECONDS_BETWEEN_DNS_RETRIES )
			{
				result = eNRR_Pending;
				m_connectionStatus = eCTCPSCS_Pending;
				m_lastNameReq = now;
				m_pNameReq = RESOLVER.RequestNameLookup( m_pUrl );
			}
		}
	}

	if ( m_pResolvedAddr )
	{
		addr.sin_family = AF_INET;

		if ( ConvertAddr( *m_pResolvedAddr, &addr ) )
		{
			result = eNRR_Succeeded;
		}
		else
		{
			result = eNRR_Failed;
		}
	}

	return result;
}


// Description:
//	 Connect socket.
// Arguments:
//	 sockIdx- socket index
// Return:
//	 Result code.
ESocketError
CCryTCPService::Connect(
	int32&					sockIdx )
{
	ESocketError			result = eSE_MiscFatalError;

	sockaddr_in				addr;
	ENameRequestResult		ret = GetAddress( addr );

	if ( ret == eNRR_Succeeded )
	{
		result = m_pOwner->Connect( addr, sockIdx );

		if ( result == eSE_Ok )
		{
			m_connectionStatus = eCTCPSCS_Connected;
			++m_numSocketsOpen;
		}
		else
		{
			m_connectionStatus = eCTCPSCS_NotConnected;
		}
	}

	return result;
}


// Description:
//	 Send data.
// Arguments:
//	 pData - transaction
// Return:
//	 Result code.
ESocketError
CCryTCPService::Send(
	STCPServiceDataPtr		pData )
{
	size_t					sent;

	ESocketError			result;

	result = m_pOwner->Send( pData->m_socketIdx, pData->pData + pData->sent, pData->length - pData->sent, sent );

	switch ( result )
	{
	case eSE_Ok:

		pData->sent += sent;
		if (sent>0)
		{
			pData->m_quietTimer=0.0f;
		}

		break;
	}

	return result;
}


// Description:
//	 Receive data.
// Arguments:
//	 pData - transaction
//	 endOfStream - has end of stream been reached
// Return:
//	 Result code.
ESocketError
CCryTCPService::Recv(
	STCPServiceDataPtr		pData,
	bool&					endOfStream )
{
	ESocketError			result;

	char					buf[ RECV_BUFFER_SIZE ];
	size_t					bufLen = sizeof ( buf );
	ECryTCPServiceResult	tcpServErr;

	switch ( result = m_pOwner->Recv( pData->m_socketIdx, buf, bufLen, endOfStream ) )
	{
	case eSE_Ok:

		tcpServErr = eCTCPSR_Ok;
		if (bufLen>0)
		{
			pData->m_quietTimer=0.0f;
		}
		break;

	default:

		tcpServErr = eCTCPSR_Failed;
		break;
	}

	if ( pData->tcpServReplyCb )
	{
	if ( pData->tcpServReplyCb( tcpServErr, pData->pUserArg, pData, buf, bufLen, endOfStream ) )
		{
			endOfStream = true;
		}
	}
	else
	{
		endOfStream = true;
	}

	return result;
}


// Description:
//	 Close connection
// Arguments:
//	 sockIdx - socket index
// Return:
//	 Result code.
ESocketError
CCryTCPService::CloseConnection(
	int32&					sockIdx )
{
	ESocketError			result = eSE_MiscFatalError;

	if ( sockIdx != INVALID_SOCKET_INDEX )
	{
		--m_numSocketsOpen;
		result = m_pOwner->CloseConnection( sockIdx );
	}

	return result;
}


// Description:
//	 Tick.
// Arguments:
//	 delta - time delta from last tick
void
CCryTCPService::Tick(
	CTimeValue				delta )
{
	if (m_dataQueueLength > 0)
	{
		if ( HasResolved() )
		{
			STCPServiceDataPtr		pSentinel = 0;
			STCPServiceDataPtr		pCurr = 0;
			STCPServiceDataPtr		pKeep = 0;

			m_numSocketsToFree = m_numSocketsOpen;

			for ( ;; )
			{
				m_queueMutex.Lock();
				{
					if ( pKeep )
					{
						// First element re-added to queue becomes sentinel to ensure
						// only one pass through the queue per tick.

						if ( !pSentinel )
						{
							pSentinel = pKeep;
						}

						m_dataQueue.push( pKeep );
						m_dataQueueLength++;
						m_dataQueueSize += pKeep->length;
					}

					if ( !m_dataQueue.empty() && ( m_dataQueue.front() != pSentinel ) && ( m_numSocketsToFree || m_pOwner->NumFreeSockets() ) )
					{
						pCurr = m_dataQueue.front();
						m_dataQueue.pop();
						m_dataQueueLength--;
						m_dataQueueSize -= pCurr->length;
					}
					else
					{
						pCurr = 0;
					}
				}
				m_queueMutex.Unlock();

				if ( !pCurr  )
				{
					break;
				}

				// Discard current element unless we decide to re-add it to the
				// queue.

				if ( m_pOwner->NumFreeSockets() ||	// Can we create a socket?
					 ( pCurr->m_socketIdx != INVALID_SOCKET_INDEX ) )	// Does this element already have a socket?
				{
					pCurr->m_quietTimer+=delta.GetSeconds();
					pKeep = ProcessTransaction( pCurr );
				}
				else
				{
					// Not enough sockets to do this one; re-add it to the queue so
					// that we can try again next tick.

					pKeep = pCurr;
				}

				if ( !pKeep )
				{
					CloseConnection( pCurr->m_socketIdx );
					pCurr->m_socketIdx = INVALID_SOCKET_INDEX;
				}
			}
		}

		ECryTCPServiceConnectionStatus connectionStatus = GetConnectionStatus();

		if ((connectionStatus == eCTCPSCS_NotConnectedDNSFailed) || (connectionStatus == eCTCPSCS_NotConnectedConnectionFailed))
		{
			// If the connection is in these states we will not get to do a ProcessTransaction so remove the data and call the callbacks with failure.
			// All other states will at some point get to a point where ProcessTransaction can be done or get to one of these states.
			m_queueMutex.Lock();

			while (!m_dataQueue.empty())
			{
				DiscardQueueElement();
			}

			CRY_ASSERT_MESSAGE((m_dataQueueLength == 0) && (m_dataQueueSize == 0), "CCryTCPService:::Tick(): m_dataQueueLength and m_dataQueueSize should be 0");

			m_queueMutex.Unlock();
		}
	}
}


// Description:
//	 Is this a match for the given service?
// Arguments:
//	 pUrl- server address
//	 port - port
// Return:
//	 true if it's a match, otherwise false.
bool
CCryTCPService::IsService(
	const char*				pUrl,
	uint16					port )
{
	
	return ( port == m_port ) && pUrl && m_pUrl && !strncmp( pUrl, m_pUrl, MAX_URL_LENGTH );
}


// Description:
//	 Has the address resolved?
// Return:
//	 true if the address has resolved, otherwise false.
bool
CCryTCPService::HasResolved()
{
	ENameRequestResult		getAddressResult;

	sockaddr_in				addr;

	getAddressResult = GetAddress( addr );

	return (getAddressResult == eNRR_Succeeded);
}
 
// Description:
//	 Is the address resolving?
// Return:
//	 true if the address is resolving, otherwise false.
bool
CCryTCPService::IsResolving()
{
	ENameRequestResult		getAddressResult;

	sockaddr_in				addr;

	getAddressResult = GetAddress( addr );

	return (getAddressResult == eNRR_Pending);
}

// UploadData
// Queues data for uploading to remote server. Note data will be deleted after upload completes/fails.
// return			- True if successfully queued, false indicates internal failure (queue full maybe)
bool
CCryTCPService::UploadData(
	STCPServiceDataPtr		pData )
{
	bool					result = false;

	if ( IsResolving() || HasResolved())
	{
		if ( pData->pData )
		{
			m_queueMutex.Lock();
			{
				m_dataQueue.push( pData );
				m_dataQueueLength++;
				m_dataQueueSize += pData->length;
			}
			m_queueMutex.Unlock();

			result = true;
		}
	}

	return result;
}


// Description:
//	 Process a transaction.
// Return:
//	 The transaction if we're not finished with it, or NULL if we are.
STCPServiceDataPtr
CCryTCPService::ProcessTransaction(
	STCPServiceDataPtr		pCurr )
{
	STCPServiceDataPtr		pResult = 0;

	ECryTCPServiceResult	result = eCTCPSR_Failed;
	bool					endOfStream=true;

	if ( pCurr->m_socketIdx == INVALID_SOCKET_INDEX )
	{
		Connect( pCurr->m_socketIdx );
	}
	else
	{
		// This is one of the elements for which processing could
		// have freed a socket.

		--m_numSocketsToFree;
	}

	ESocketError			sendResult = eSE_Ok;

	if ( pCurr->sent < pCurr->length )
	{
		sendResult = Send( pCurr );

		switch ( sendResult )
		{
		case eSE_Ok:

			result = eCTCPSR_Ok;
			break;

		default:

			result = eCTCPSR_Failed;
			break;
		}
	}

	if ( ( ( pCurr->sent == pCurr->length ) || ( sendResult != eSE_Ok ) ) && pCurr->tcpServCb )
	{
		// All data sent or send failed, no reply expected, invoke callback.

		pCurr->tcpServCb( result, pCurr->pUserArg );
	}
	else if ( ( pCurr->sent == pCurr->length ) && pCurr->tcpServReplyCb )
	{
		// All data sent, reply expected, read data from socket.

		Recv( pCurr, endOfStream );

		if ( !endOfStream )
		{
			pResult = pCurr;
		}
	}
	else if ( ( sendResult != eSE_Ok ) && pCurr->tcpServReplyCb )
	{
		// Send failed, reply expected, invoke callback.

		pCurr->tcpServReplyCb( eCTCPSR_Failed, pCurr->pUserArg, pCurr, 0, 0, true );
	}
	else
	{
		pResult = pCurr;
	}

	return pResult;
}


// Description:
//	 Increment reference count.
// Return:
//	 Reference count
int
CCryTCPService::AddRef()
{
	return CryInterlockedIncrement( &m_cnt );
}


// Description:
//	 Decrement reference count.
// Return:
//	 Reference count
int
CCryTCPService::Release()
{
	const int				nCount = CryInterlockedDecrement( &m_cnt );

	assert( nCount >= 0 );

	if ( nCount == 0 )
	{
		delete this;
	}
	else
	{
		if ( nCount < 0 )
		{
			assert( 0 );
			CryFatalError( "Deleting Reference Counted Object Twice" );
		}
	}

	return nCount;
}

ECryTCPServiceConnectionStatus CCryTCPService::GetConnectionStatus()
{
	return m_connectionStatus;
}

uint32 CCryTCPService::GetDataQueueLength()
{
	return m_dataQueueLength;
}

uint32 CCryTCPService::GetDataQueueSize()
{
	return m_dataQueueSize;
}
